/**
 * Copyright (C) 2016-2019 DSpot Sp. z o.o
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dspot.declex.parser;

import com.dspot.declex.util.FileUtils;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.logger.Logger;
import org.ohosannotations.rclass.IRClass.Res;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class LayoutsParser {

    private Logger LOGGER;

    private Map<String, Map<String, LayoutObject>> layoutMaps = new HashMap<>();

    private List<File> layoutFolders = new LinkedList<File>();

    private ProcessingEnvironment processingEnv;
    private OhosAnnotationsEnvironment environment;

    private static LayoutsParser instance;

    public static LayoutsParser getInstance() {
        return instance;
    }

    public LayoutsParser(OhosAnnotationsEnvironment environment, Logger logger) {
        this.LOGGER = logger;
        this.processingEnv = environment.getProcessingEnvironment();
        this.environment = environment;

        File resFolderFile = FileUtils.getResFolder(processingEnv);
        if (resFolderFile.exists()) {
            try {
                LOGGER.info("Layout Parsing in: " + resFolderFile.getCanonicalPath());
            } catch (IOException e) {
                e.printStackTrace();
            }

            for (File file : resFolderFile.listFiles()) {
                if (file.getName().equals("base")) {
                    for (File listFile : file.listFiles()) {
                        if (listFile.isDirectory() && (listFile.getName().equals("layout"))) {
                            layoutFolders.add(listFile);
                        }
                    }
                }
            }

            LOGGER.info("Layout Folders Found: " + layoutFolders);
        } else {
            LOGGER.info("Layout Folders Not Found");
        }


        LayoutsParser.instance = this;
    }

    public Map<String, LayoutObject> getLayoutObjects(String layoutName) {
        return getLayoutObjects(layoutName, null);
    }

    private Map<String, LayoutObject> getLayoutObjects(String layoutName, String layoutId) {
        Map<String, LayoutObject> layoutObjects = layoutMaps.get(layoutName);

        if (layoutObjects == null) {
            for (File layout : layoutFolders) {
                for (File file : layout.listFiles()) {
                    if (file.isFile() && file.getName().equals(layoutName + ".xml")) {

                        layoutObjects = parseLayout(file, layoutId);

                        if (layoutMaps.containsKey(layoutName)) {
                            //Merge layouts
                            layoutMaps.get(layoutName).putAll(layoutObjects);
                        } else {
                            layoutMaps.put(layoutName, layoutObjects);
                        }

                    }
                }
            }
        }

        return layoutObjects;
    }

    private void searchInNode(Element node, Map<String, LayoutObject> foundObjects, String layoutId) {
        //documentElement.normalize();

        final String[] packages = {
            "ohos.agp.components."
        };

        //Navigate in the <include> tag
        if (node.getTagName().equals("include")) {
            if (node.hasAttribute("ohos:layout")) {

                String id = null;
                if (node.hasAttribute("ohos:id")) {
                    id = node.getAttribute("ohos:id");
                    id = id.substring(id.lastIndexOf(':') + 1);
                }

                String layoutName = node.getAttribute("layout");
                if (layoutName == null || "".equals(layoutName)) {
                    layoutName = node.getAttribute("ohos:layout");
                }

                layoutName = layoutName.substring(layoutName.lastIndexOf(':') + 1);

                foundObjects.putAll(getLayoutObjects(layoutName, id));
            }
            return;
        }

        if (node.hasAttribute("ohos:id") || layoutId != null) {

            String id;
            if (layoutId != null)
                id = layoutId;
            else {
                id = node.getAttribute("ohos:id");
                id = id.substring(id.lastIndexOf(':') + 1);
            }

            String className = node.getTagName();

            //Find canonical name of the class if needed
            if (!className.contains(".")) {
                for (String pkg : packages) {
                    String testName = pkg + className;
                    TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(testName);
                    if (typeElement != null) {
                        className = testName;
                        break;
                    }
                }
            }

            if (containsField(id, Res.ID)) {
                LayoutObject layoutObject = new LayoutObject(id, className, node);
                foundObjects.put("Id_" + id, layoutObject);
            }
        }

        NodeList nodes = node.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++)
            if (nodes.item(i) instanceof Element) {
                searchInNode((Element) nodes.item(i), foundObjects, null);
            }
    }

    private Map<String, LayoutObject> parseLayout(File xmlLayoutFile, String layoutId) {
        LOGGER.info("Layout Parsing: " + xmlLayoutFile.getName());

        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();

        Document doc;
        try {
            docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            doc = docBuilder.parse(xmlLayoutFile);
        } catch (Exception e) {
            LOGGER.error("Could not parse Layout file at path {}", xmlLayoutFile.getName(), e);
            return new HashMap<>();
        }

        Map<String, LayoutObject> foundObjects = new TreeMap<>();
        Element documentElement = doc.getDocumentElement();

        searchInNode(documentElement, foundObjects, layoutId);

        LOGGER.info("Layout Parsing Found: " + foundObjects);

        return foundObjects;
    }

    private boolean containsField(String name, Res res) {
		/*原代码IRInnerClass rInnerClass = environment.getRClass().get(res);
		return rInnerClass.containsField(name);*/
        name = "Id_" + name;
        return environment.getRClass().containsField(name);

    }

    public static class LayoutObject {

        public String id;

        public String className;
        public Element domElement;

        public String holderId; //Used by NavigationView

        public LayoutObject(String id, String className, Element domElement) {
            this.id = id;
            this.className = className;
            this.domElement = domElement;
        }

        @Override
        public String toString() {
            return className;
        }
    }
}
